// g++ testsdl.cpp -lSDL -lavformat -lavcodec -lavutil -lswscale
#include <stdlib.h>
#include <string.h>

extern "C"
{
#include <libswscale/swscale.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavformat/avio.h>
#include <libavutil/imgutils.h>
#include <SDL/SDL.h>
#include <SDL/SDL_thread.h>
};

//#include "decoder.h"

// 音频播放器
//#include "wave.h"
//#define _WAVE_
//全局变量---------------------
static Uint8 *audio_chunk;
static Uint32 audio_len;
static Uint8 *audio_pos;

//-----------------
/* The audio function callback takes the following parameters:
stream: A pointer to the audio buffer to be filled
len: The length (in bytes) of the audio buffer (这是固定的 4096?)
回调函数
注意:mp3 为什么播放不顺畅?
len=4096;audio_len=4608;两个相差 512!为了这 512,还得再调用一次回调函数。。。
m4a,aac 就不存在此问题(都是 4096)!
*/
void fill_audio(void *udata, Uint8 *stream, int len)
{
    /* Only play if we have data left */
    if (audio_len == 0)
        return;
    /* Mix as much data as possible */
    len = (len > audio_len ? audio_len : len);
    SDL_MixAudio(stream, audio_pos, len, SDL_MIX_MAXVOLUME);
    audio_pos += len;
    audio_len -= len;
}

#define AVCODEC_MAX_AUDIO_FRAME_SIZE 192000

int decode_audio(char *no_use)
{
    AVFormatContext *pFormatCtx;
    int i, audioStream;
    AVCodecContext *pCodecCtx;
    AVCodec *pCodec;
    char url[300] = {0};
    strcpy(url, no_use);
    // Register all available file formats and codecs
    // av_register_all();
    //支持网络流输入
    avformat_network_init();
    //初始化
    pFormatCtx = avformat_alloc_context();
    //有参数 avdic
    // if(avformat_open_input(&pFormatCtx,url,NULL,&avdic)!=0){
    if (avformat_open_input(&pFormatCtx, url, NULL, NULL) != 0)
    {
        printf("Couldn't open file.\n");
        return -1;
    }

    // Retrieve stream information
    if (avformat_find_stream_info(pFormatCtx, NULL) < 0)
    {
        printf("Couldn't find stream information.\n");
        return -1;
    }

    // Dump valid information onto standard error
    av_dump_format(pFormatCtx, 0, url, false);
    // Find the first audio stream
    audioStream = -1;
    for (i = 0; i < pFormatCtx->nb_streams; i++)
        //原为 codec_type==CODEC_TYPE_AUDIO
        if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
        {
            audioStream = i;
            break;
        }

    if (audioStream == -1)
    {
        printf("Didn't find a audio stream.\n");
        return -1;
    }

    // Get a pointer to the codec context for the audio stream
    auto codep = pFormatCtx->streams[audioStream]->codecpar;

    // Find the decoder for the audio stream
    pCodec = avcodec_find_decoder(codep->codec_id);
    if (pCodec == NULL)
    {
        printf("Codec not found.\n");
        return -1;
    }

    // Open codec
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
    {
        printf("Could not open codec.\n");
        return -1;
    }

    /********* For output file ******************/
    FILE *pFile;
#ifdef _WAVE_
    pFile = fopen("output.wav", "wb");
    fseek(pFile, 44, SEEK_SET); //预留文件头的位置
#else
    pFile = fopen("output.pcm", "wb");
#endif
    // Open the time stamp file
    FILE *pTSFile;
    pTSFile = fopen("audio_time_stamp.txt", "wb");
    if (pTSFile == NULL)
    {
        printf("Could not open output file.\n");
        return -1;
    }

    fprintf(pTSFile, "Time Base: %d/%d\n", pCodecCtx->time_base.num, pCodecCtx->time_base.den);
    /*** Write audio into file ******/
    //把结构体改为指针
    AVPacket *packet = (AVPacket *)malloc(sizeof(AVPacket));
    av_init_packet(packet);
    //音频和视频解码更加统一!
    //新加
    AVFrame *pFrame = av_frame_alloc();
    //---------SDL--------------------------------------
    //初始化
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER))
    {
        printf("Could not initialize SDL - %s\n", SDL_GetError());
        exit(1);
    }

    //结构体,包含 PCM 数据的相关信息
    SDL_AudioSpec wanted_spec;
    wanted_spec.freq = pCodecCtx->sample_rate;
    wanted_spec.format = AUDIO_S16SYS;
    wanted_spec.channels = pCodecCtx->channels;
    wanted_spec.silence = 0;
    wanted_spec.samples = 1024; //播放 AAC,M4a,缓冲区的大小
    // wanted_spec.samples = 1152; //播放 MP3,WMA 时候用
    wanted_spec.callback = fill_audio;
    wanted_spec.userdata = pCodecCtx;
    if (SDL_OpenAudio(&wanted_spec, NULL) < 0) //步骤(2)打开音频设备
    {
        printf("can't open audio.\n");
        return 0;
    }

    //-----------------------------------------------------
    printf("比特率 %3d\n", pFormatCtx->bit_rate);
    printf("解码器名称 %s\n", pCodecCtx->codec->long_name);
    printf("time_base %d \n", pCodecCtx->time_base);
    printf("声道数 %d \n", pCodecCtx->channels);
    printf("sample per second %d \n", pCodecCtx->sample_rate);
    //新版不再需要
    //
    //
    short decompressed_audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2];
    int decompressed_audio_buf_size;
    uint32_t ret, len = 0;
    int got_picture;
    int index = 0;
    while (av_read_frame(pFormatCtx, packet) >= 0)
    {
        if (packet->stream_index == audioStream)
        {
            // decompressed_audio_buf_size = (AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2;
            //原为 avcodec_decode_audio2
            // ret = avcodec_decode_audio4( pCodecCtx, decompressed_audio_buf,
            //&decompressed_audio_buf_size, packet.data, packet.size );
            //改为
            ret = avcodec_decode_audio4(pCodecCtx, pFrame,
                                        &got_picture, packet);
            // if error len = -1
            if (ret < 0)
            {
                printf("Error in decoding audio frame.\n");
                exit(0);
            }

            if (got_picture > 0)
            {
#if 1
                printf("index %3d\n", index);
                printf("pts %5d\n", packet->pts);
                printf("dts %5d\n", packet->dts);
                printf("packet_size %5d\n", packet->size);
// printf("test %s\n", rtmp->m_inChunkSize);
#endif
                //直接写入
                //注意:数据是 data【0】, 长度是 linesize【0】
#if 1
                fwrite(pFrame->data[0], 1, pFrame->linesize[0], pFile);
                // fwrite(pFrame, 1, got_picture, pFile);
                // len+=got_picture;
                index++;
// fprintf(pTSFile, "%4d,%5d,%8d\n", index, decompressed_audio_buf_size, packet.pts);
#endif
            }
#if 1
            //---------------------------------------
            // printf("begin....\n");
            //设置音频数据缓冲,PCM 数据
            audio_chunk = (Uint8 *)pFrame->data[0];
            //设置音频数据长度
            audio_len = pFrame->linesize[0];
            // audio_len = 4096;
            //播放 mp3 的时候改为 audio_len = 4096
            //则会比较流畅,但是声音会变调!MP3 一帧长度 4608
            //使用一次回调函数(4096 字节缓冲)播放不完,所以还要使用一次回调函数,导致播放缓慢。。
            //设置初始播放位置
            audio_pos = audio_chunk;
            //回放音频数据
            SDL_PauseAudio(0);

            // printf("don't close, audio playing...\n");
            while (audio_len > 0) //等待直到音频数据播放完毕!
                SDL_Delay(1);
//---------------------------------------
#endif
        }
        // Free the packet that was allocated by av_read_frame
        //已改
        av_free_packet(packet);
    }

// printf("The length of PCM data is %d bytes.\n", len);
#ifdef _WAVE_
    fseek(pFile, 0, SEEK_SET);
    struct WAVE_HEADER wh;
    memcpy(wh.header.RiffID, "RIFF", 4);
    wh.header.RiffSize = 36 + len;
    memcpy(wh.header.RiffFormat, "WAVE", 4);
    memcpy(wh.format.FmtID, "fmt ", 4);
    wh.format.FmtSize = 16;
    wh.format.wavFormat.FormatTag = 1;
    wh.format.wavFormat.Channels = pCodecCtx->channels;
    wh.format.wavFormat.SamplesRate = pCodecCtx->sample_rate;
    wh.format.wavFormat.BitsPerSample = 16;
    calformat(wh.format.wavFormat); // Calculate AvgBytesRate and BlockAlign
    memcpy(wh.data.DataID, "data", 4);
    wh.data.DataSize = len;
    fwrite(&wh, 1, sizeof(wh), pFile);
#endif

    //关闭音频设备
    SDL_CloseAudio();
    // Close file
    fclose(pFile);
    // Close the codec
    avcodec_close(pCodecCtx);
    // Close the video file
    avformat_close_input(&pFormatCtx);
    return 0;
}

// 视频播放器
int main(int argc, char *argv[])
{
    int i, videoindex;
    char filepath[] = "/home/ljd/Videos/跑步机遛猫.mp4";

    av_register_all();
    avformat_network_init();

    auto pFormatCtx = avformat_alloc_context();
    if (avformat_open_input(&pFormatCtx, filepath, NULL, NULL) != 0)
    {
        printf("无法打开文件\n");
        return -1;
    }

    if (avformat_find_stream_info(pFormatCtx, NULL) < 0)
    {
        printf("Couldn't find stream information.\n");
        return -1;
    }

    videoindex = -1;
    for (i = 0; i < pFormatCtx->nb_streams; i++)
        if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            videoindex = i;
            break;
        }

    if (videoindex == -1)
    {
        printf("Didn't find a video stream.\n");
        return -1;
    }

    auto codep = pFormatCtx->streams[videoindex]->codecpar;

    auto pCodec = avcodec_find_decoder(codep->codec_id);
    if (pCodec == NULL)
    {
        printf("Codec not found.\n");
        return -1;
    }

    auto pCodecCtx = avcodec_alloc_context3(codec);
    avcodec_parameters_to_context(pCodecCtx, codep);
    if (pCodecCtx == NULL)
    {
        printf("Cannot alloc context.");
        return -1;
    }

    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
    {
        printf("Could not open codec.\n");
        return -1;
    }

    auto pFrame = av_frame_alloc();
    auto pFrameYUV = av_frame_alloc();
    uint8_t *out_buffer = new uint8_t[av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1)];

    av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize,
                         out_buffer,
                         AV_PIX_FMT_YUV420P,
                         pCodecCtx->width,
                         pCodecCtx->height, 1);

    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER))
    {
        printf("Could not initialize SDL - %s\n", SDL_GetError());
        exit(1);
    }

    auto screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 0, 0);
    if (!screen)
    {
        printf("SDL: could not set video mode - exiting\n");
        exit(1);
    }

    auto bmp = SDL_CreateYUVOverlay(pCodecCtx->width, pCodecCtx->height, SDL_YV12_OVERLAY, screen);
    SDL_Rect rect;
    int ret;
    int y_size = pCodecCtx->width * pCodecCtx->height;
    auto packet = av_packet_alloc();
    av_new_packet(packet, y_size);

    //输出一下信息
    printf("文件信息-----------------------------------------\n");
    av_dump_format(pFormatCtx, 0, filepath, 0);
    printf("-------------------------------------------------\n");

    auto img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);

    // 读帧
    while (av_read_frame(pFormatCtx, packet) >= 0)
    {
        if (packet->stream_index == videoindex)
        {
            ret = avcodec_send_packet(pCodecCtx, packet);
            if (ret == 0)
            {
                while (avcodec_receive_frame(pCodecCtx, pFrame) == 0)
                {
                    sws_scale(img_convert_ctx, (const uint8_t *const *)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);

                    SDL_LockYUVOverlay(bmp);
                    bmp->pixels[0] = pFrameYUV->data[0];
                    bmp->pixels[2] = pFrameYUV->data[1];
                    bmp->pixels[1] = pFrameYUV->data[2];
                    bmp->pitches[0] = pFrameYUV->linesize[0];
                    bmp->pitches[2] = pFrameYUV->linesize[1];
                    bmp->pitches[1] = pFrameYUV->linesize[2];
                    SDL_UnlockYUVOverlay(bmp);

                    rect.x = 0;
                    rect.y = 0;
                    rect.w = pCodecCtx->width;
                    rect.h = pCodecCtx->height;
                    SDL_DisplayYUVOverlay(bmp, &rect);

                    //延时 40ms
                    SDL_Delay(40);
                }
            }
        }

        av_free_packet(packet);
    }

    delete[] out_buffer;
    av_free(pFrameYUV);
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFormatCtx);
    return 0;
}
